# -*- coding:utf-8 -*-
#  FileName    : SadFarmer.py 
#  Author      : WangMaoMao
#  Description : 校内(人人)开心农场辅助工具 伤心农民 0.1.2 
#  Time-stamp: <2009-08-25 19:23:28 maomao> 



import socket
socket.setdefaulttimeout(10.0)
import urllib
import urllib2
import time
import simplejson
import md5
import logging
import re
import os

class HappyFarm(object):
    def __init__(self, email=None, password=None, autoInit=True):
        self.inited = False
        self._timeDelta = 0
        self._timeStamp = 0
        self._stateChanged = False      # 标志变量
        self.jsonDecode = simplejson.JSONDecoder(encoding='gb18030').decode
        cookie_handler = urllib2.HTTPCookieProcessor()
        self.opener = urllib2.build_opener(cookie_handler)
        self.opener.addheaders = [('User-agent', 'Mozilla/5.0 SadFarmer/0.1 By WangMaoMao'),
                                  ('Referer', 'http://xn.cache.fminutes.com/images/v3/module/Main.swf?v=4'),
                                  ('x-flash-version', '11,0,32,18'), # flash 11 better?
                                  ]
        if email and password:
            self.login(email, password)
        else:
            try:
                cookie = self.getCookieViaCOM()
            except :
                logging.error("无法通过 COM 连接获得用户信息!")
                os.system('pause')
                exit(-1)
            self.opener.addheaders += [ ('Cookie', cookie) ]
        if autoInit:
            self.initFarm()
    def initFarm(self):
        self._initMyInfo()
        self._initShopInfo()
        self.updateMyPackage()
        self.updateAllFarms()
        self._timeStamp = self.now()
        self.inited = True
    def _initMyInfo(self):
        res = self.request( self.buildUrl('user', 'run') )
        logging.info("初始化自己农场信息.")
        logging.info("用户: %s 服务器时间: %d 土地数: %d.",
                     res.get('user', {}).get('userName', 0).encode('gb18030'),
                     res.get('serverTime', {}).get('time', 0),
                     len(res.get('farmlandStatus', [])) )
        self._uid = res['user']['uId']
        self._farmlandStatus = res['farmlandStatus'] # or needless
        self._timeDelta = int(time.time() - res['serverTime']['time'])
        logging.info("时间同步成功.")
    def updateFarm(self, ownerId=0):
        res = self.request( self.buildUrl('user', 'run', 1),
                            {'ownerId': ownerId} )
        if ownerId== 0:
            self._farmlandsStatus[self._uid] = res['farmlandStatus']
            self._farmlandStatus = res['farmlandStatus']
            logging.info("更新自己农场信息. 土地数: %d.", len(res['farmlandStatus']) )
        else:
            self._farmlandsStatus[ownerId] = res['farmlandStatus']
            logging.info("更新用户 %s(id:%s) 农场信息. 土地数: %d.",
                         self.id2userName(ownerId),
                         str(ownerId), len(res['farmlandStatus']) )

    def updateMyPackage(self):
        res = self.request( self.buildUrl('Package', 'getPackageInfo') )
        logging.info("更新背包信息 类型1(种子).")
        for i in res.get('1', []):  # May fail, so use get()
            logging.info("%s %d 个", i['cName'].encode('gb18030'), int(i['amount']) )
        self._packageInfo = res.get('1', [])
    def updateAllFarms(self):
        res = self.request( self.buildUrl('friend'),
                            {'fv': 0,
                             'refresh': 'true'} )
        self.userList = [f['userId'] for f in res if int(f['exp'])!= 0] # note: last is mine
        self.userDict = dict([(f['userId'], f) for f in res if int(f['exp'])!= 0])
        self._farmlandsStatus = {}
        for uid in self.userList:
            self.updateFarm(uid)
        logging.info("获得 %d 个用户农场信息.", len(self.userList))
        self._farmlandStatus = self._farmlandsStatus[self._uid]
    def _initShopInfo(self):
        res = self.request( self.buildUrl('shop', 'getShopInfo', type=[1]) )
        logging.info("初始化商店物品 类型1(种子).")
        self._shopInfo = res['1']
    def harvest(self, ownerId=None):
        ownerId = ownerId if ownerId else self._uid
        # harvest my only
        for i, land in enumerate(self._farmlandStatus):
            if land['b'] == 6: # 成熟
                self._stateChanged = True
                logging.info("土地%d %s 成熟. 收获!", i, self.id2cName(land['a']))
                res = self.request( self.buildUrl('farmlandstatus', 'harvest'),
                                    {'ownerId': ownerId,
                                     'place'  : i } )
                self.log(res)
        if self._stateChanged:
            self.updateFarm()
            self._stateChanged = False
    def buy(self, howMany=1, id=None, type=1):
        if not id:
            logging.warning("由于王猫猫喜欢玫瑰, 所以你也必须种玫瑰!")
            id = 101
            type = 1
        logging.info("购买 %s(类型%d) %d 个",
                     self.id2cName(id), type, howMany)
        res = self.request( self.buildUrl('shop', 'buy'),
                            {'id' : id,
                             'type' : type,
                             'number' : howMany } )
        self.log(res)
        
    def scarify(self, ownerId=None):  # 铲除
        ownerId = ownerId if ownerId else self._uid
        # scarify my only
        for i, land in enumerate(self._farmlandStatus):
            if land['b'] == 7: # 枯根状态
                self._stateChanged = True
                logging.info("土地%d %s 枯萎. 铲除!", i, self.id2cName(land['a']))
                res = self.request( self.buildUrl('farmlandstatus', 'scarify'),
                                    {'ownerId': ownerId,
                                     'place'  : i } )
                self.log(res)
        if self._stateChanged:
            self.updateFarm()
            self._stateChanged = False

    def planting(self, ownerId=None):
        ownerId = ownerId if ownerId else self._uid
        emptyLands = [i for i,land in enumerate(self._farmlandStatus) if land['b']==0]
        seedNeeded = len(emptyLands)
        if seedNeeded<= 0:
            logging.info("不需要种植新作物.")
            return 
        logging.info("发现空地 %d 块.", seedNeeded)
        cItems = sorted(self._packageInfo, key = lambda d:d['cId'])[::-1]
        seedHaveGot = sum(seed['amount'] for seed in cItems)
        if seedNeeded> seedHaveGot:
            logging.warning("种子不足, 自动购买.")
            self.buy(seedNeeded - seedHaveGot)
            self.updateMyPackage()
            return self.planting(ownerId)
        for i in emptyLands:
            for cItem in cItems:
                if cItem['amount']<= 0:
                    continue
                logging.info("土地%d 种植 %s.", i, self.id2cName(cItem['cId']))
                res = self.request( self.buildUrl('farmlandstatus', 'planting'),
                                    {'ownerId' : ownerId,
                                     'cId' : cItem['cId'],
                                     'place' : i } )
                self.log(res)
                break
        self.updateMyPackage()
        self.updateFarm() # default to mine
        
    def doMisc(self, ownerId=None):
        ownerId = ownerId if ownerId else self._uid
        for i, land in enumerate( self._farmlandsStatus[ownerId] ):
            if land['h'] == 0: # 干旱
                self._stateChanged = True
                logging.info("用户 %s(id:%d) 土地%d %s 干旱. 帮助浇水!", self.id2userName(ownerId),
                             ownerId, i, self.id2cName(land['a']))
                res = self.request( self.buildUrl('farmlandstatus', 'water'),
                                    {'ownerId': ownerId,
                                     'place'  : i } )
                self.log(res)
            if land['t'] != 0: # 青虫血量
                land['g']+= 1
            for _ in xrange( land['g'] ): # 虫子数
                self._stateChanged = True
                logging.info("用户 %s(id:%d) 土地%d %s 有虫子. 帮助杀虫!", self.id2userName(ownerId),
                             ownerId, i, self.id2cName(land['a']))
                res = self.request( self.buildUrl('farmlandstatus', 'spraying'),
                                    {'tId' : 0,
                                     'ownerId': ownerId,
                                     'place'  : i } )
                self.log(res)
            for _ in xrange( land['f'] ): # 草数, FIXME: 可能不对
                self._stateChanged = True
                logging.info("用户 %s(id:%d) 土地%d %s 长草. 帮助除草!", self.id2userName(ownerId),
                             ownerId, i, self.id2cName(land['a']))
                res = self.request( self.buildUrl('farmlandstatus', 'clearWeed'),
                                    {'tId' : 0,
                                     'ownerId': ownerId,
                                     'place'  : i } )
                self.log(res)
        if self._stateChanged:
            self.updateFarm(ownerId)
            self._stateChanged = False
            
    def scrounge(self, ownerId): # 偷菜
        if ownerId == self._uid:
            return # 不偷自己的
        for i, land in enumerate( self._farmlandsStatus[ownerId] ):
            if land['b'] == 6 and land['m'] > land['l'] and land['n']==2:
                self._stateChanged = True
                logging.info("用户 %s(id:%d) 土地%d %s 可偷窃.", self.id2userName(ownerId),
                             ownerId, i, self.id2cName(land['a']))
                res = self.request( self.buildUrl('farmlandstatus', 'scrounge'),
                                    {'ownerId': ownerId,
                                     'place'  : i } )
                self.log(res)
        if self._stateChanged:
            self.updateFarm(ownerId)
            self._stateChanged = False

    def runSimple(self):
        if not self.inited:
            self.initFarm()
        self.harvest()
        self.scarify()
        self.planting()
        # self._doMisc()
        for i in self.userList:
            self.doMisc(i) # 先做好事后偷人
            self.scrounge(i)
        self._timeStamp = self.now()

        # adds more here
    def refresh(self):
        self.updateAllFarms()
        self.updateMyPackage()
        self._timeStamp = self.now()
    
    def log(self, res, additionalInfo = '.'):
        message = []
        if 'direction' in res:
            message.append(res['direction'].encode('gb18030'))
        if 'harvest' in res:
            message.append("收获 %s" % str(res['harvest']) )
        if 'money' in res:
            message.append("金钱 %s" % str(res['money']) )
        if 'exp' in res:
            message.append("经验 %s" % str(res['exp']) )
        if 'charm' in res:
            message.append("魅力 %s" % str(res['charm']) )
        if 'status' in res:
            message.append("作物 %s" % self.id2cName(res['status']['cId']))
        if 'cName' in res:
            message.append(res['cName'].encode('gb18030'))
        if 'num' in res:
            message.append("%s 个" % str(res['num']))
        message.append(additionalInfo)
        logging.info(' '.join(message))

    def id2cName(self, cid): # corn name 
        return [c['cName'].split()[0] for c in self._shopInfo if c['cId']==int(cid)][0].encode('gb18030')
    def id2userName(self, uid):
        return self.userDict.get(int(uid), {}).get('userName', '囧').encode('gb18030')

    def login(self, email, password):
        logging.info("模拟页面登陆... 用户: %s.", email)
        url = "http://login.renren.com/Login.do"
        data = {'email' : email,
                'password' :  password,
                'origURL' : "http://apps.renren.com/happyfarm" }
        req = urllib2.Request(url, data=urllib.urlencode(data))
        res = self.opener.open(req)
        htmltext = res.read()
        url = re.findall(r'<iframe name="iframe_canvas" src="([^"]+)"', htmltext)
        if not url:
            logging.error("用户名/密码错误!")
            os.system('pause')
            exit(-1)
        url = url[0]
        url = url.replace('amp;', '').replace('?', '/?')
        logging.debug(url)
        req = urllib2.Request(url)
        req.add_header("Referer", "http://apps.renren.com/happyfarm?origin=104")        
        res = self.opener.open(req)
        
    def getCookieViaCOM(self):
        # win32 only, needs `save password` option on
        logging.info("使用 COM 获得保存用户登陆信息...")
        logging.warning("请确认 IE 中设置为自动登陆!")
        import win32com.client
        bro = win32com.client.Dispatch("InternetExplorer.Application")
        # show window
        bro.Visible = True # toggle debug on or off
        bro.Navigate("http://apps.renren.com/happyfarm")    
        while bro.Busy:
            pass
        bro.Navigate("http://xn.hf.fminutes.com/api.php?mod=user&act=run&farmKey=97360611586906f6c79f11a02f66a72e&farmTime=1251042130&inuId=")
        while bro.Busy:
            pass # TODO: Add time out here
        while 'xn_sig_session_key' not in bro.Document.cookie:
            pass
        cookie = str(bro.Document.cookie)
        bro.Quit()
        return cookie
    
    def request(self, url, data = {}):
        dataEncoded = urllib.urlencode(data)
        if data:
            req = urllib2.Request(url, data = dataEncoded)
        else:
            req = urllib2.Request(url)
        try:
            res = self.opener.open(req)
        except urllib2.URLError, err:
            logging.warning("获取 URL 发生错误: %s.", err)
            logging.warning("重试......")
            return self.request(url, data)
        except KeyboardInterrupt:
            logging.error("用户打断!")
            exit(-1)
        return self.jsonDecode(  res.read() )
    
    def buildUrl(self, mod, act=None, flag=None, type=[], farmTime=None):
        result_url = ["http://xn.hf.fminutes.com/api.php?mod=" + mod]
        if act:
            result_url.append("act=" + act)
        if flag:
            result_url.append("flag=" + str(flag))
        if type:
            result_url.append("type=" + ','.join(map(str, type)))
        if not farmTime:                    # serverTime
            farmTime = str(int(time.time()) - self._timeDelta)
        farmKey = md5.new(farmTime + "15L3H4KH".lower()).hexdigest() # Magic~
        # wahaha~magic erlang:integer_to_list(16#ff2e301a, 23).
        result_url.append("farmKey=" + farmKey)
        result_url.append("farmTime=" + farmTime)
        result_url.append("inuId=")
        return '&'.join(result_url)
    
    def now(self):
        return int(time.time() - self._timeDelta)
    


__doc__ = '='*80 + \
"""               伤心农民 0.1.2 release for renren.com
                    By 王猫猫(xxxxxxxx@gmail.com)
                     Tue Aug 25 15:01:48 2009
                 #使用本程序所引起的任何后果, 本人不负责#
                      请不要频繁执行, 防止被封号!
                  使用方法:
                       1> 直接运行
                       2> 察看帮助 --help
""" + '='*80


if __name__ == '__main__':
    print __doc__
    from optparse import OptionParser
    usage = "usage: %prog [options]"
    parser = OptionParser(usage=usage, version="%prog 0.1.2")
    parser.add_option("-f", "--log", "--log-file", default="./sadfarmer.log",
                      action="store", type="string", dest="logfile", metavar="FILE",
                      help=u"记录日志文件名")
    parser.add_option("-u", "--user", "--username", "--email",
                      action="store", type="string", dest="email", metavar="EMAIL",
                      help=u"用户 email, 即登陆名")
    parser.add_option("-p", "--pass", "--password",
                      action="store", type="string", dest="password", metavar="PASSWORD",
                      help=u"用户密码")
    (options, args) = parser.parse_args()
    # set logging
    print "日志写入到 %s." % options.logfile 
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s %(levelname)s: %(message)s',
                        datefmt='%Y %b %d %H:%M:%S',
                        filename = options.logfile,
                        filemode='a')
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', '%H:%M:%S')
    console.setFormatter(formatter)
    logging.getLogger('').addHandler(console)

    logging.info("================ 程序启动 ==================")
    h = HappyFarm(options.email, options.password)
    logging.info("尝试 runSimple!")
    h.runSimple()

